﻿using System;
using System.Diagnostics.Contracts;

namespace OpenWaves
{
    [ContractClass(typeof(UrlContract))]
    public abstract partial class Url : IEquatable<Url>
    {
        [Obsolete("Use Url.FromUri.", true)]
        public static Url Create(Uri uri)
        {
            return FromUri(uri);
        }

        public static Url FromUri(Uri uri)
        {
            if (uri.IsAbsoluteUri)
            {
                if (uri.Scheme == Uri.UriSchemeFile)
                    return new FileUrl(uri);

                return new AbsoluteUrl(uri);
            }

            return new RelativeUrl(uri);
        }

        public static Url Parse(string value)
        {
            Contract.Requires<ArgumentNullException>(String.IsNullOrEmpty(value) == false);
            Contract.Ensures(Contract.Result<Url>() != null);

            return FromUri(new Uri(value, UriKind.RelativeOrAbsolute));
        }

        public static bool TryParse(string value, out Url url)
        {
            Contract.Ensures(Contract.Result<bool>() == false || Contract.ValueAtReturn(out url) != null);

            url = TryParse(value, UriKind.RelativeOrAbsolute);

            return url != null;
        }

        public static RelativeUrl ParseRelative(string value)
        {
            Contract.Requires<ArgumentNullException>(String.IsNullOrEmpty(value) == false);
            Contract.Ensures(Contract.Result<RelativeUrl>() != null);

            return new RelativeUrl(new Uri(value, UriKind.Relative));
        }

        public static bool TryParseRelative(string value, out RelativeUrl url)
        {
            Contract.Ensures(Contract.Result<bool>() == false || Contract.ValueAtReturn(out url) != null);

            url = (RelativeUrl)TryParse(value, UriKind.Relative);

            return url != null;
        }

        public static AbsoluteUrl ParseAbsolute(string value)
        {
            Contract.Requires<ArgumentNullException>(String.IsNullOrEmpty(value) == false);
            Contract.Ensures(Contract.Result<AbsoluteUrl>() != null);

            return new AbsoluteUrl(new Uri(EnsureSchemaDelimiter(value), UriKind.Absolute));
        }

        public static bool TryParseAbsolute(string value, out AbsoluteUrl url)
        {
            Contract.Ensures(Contract.Result<bool>() == false || Contract.ValueAtReturn(out url) != null);

            url = (AbsoluteUrl)TryParse(EnsureSchemaDelimiter(value), UriKind.Absolute);

            return url != null;
        }

        public static AbsoluteUrlBuilder Http(string host)
        {
            return new AbsoluteUrlBuilder { Host = host };
        }

        public static AbsoluteUrlBuilder Http(string host, int port)
        {
            return new AbsoluteUrlBuilder { Host = host, Port = port };
        }

        private readonly Uri uri;

        protected Url(Uri uri)
        {
            Contract.Requires<ArgumentNullException>(uri != null);

            this.uri = uri;
        }

        public abstract UrlPath Path { get; }

        public abstract UrlQueryCollection Query { get; }

        public abstract string Fragment { get; }

        public Uri Uri
        {
            get { return uri; }
        }

        public override string ToString()
        {
            return this.ToUrlString();
        }

        public Url Combine(UrlPath path)
        {
            return this.GetBuilder().SetPath(this.Path.Combine(path)).ToUrl();
        }

        public Url Combine(string path)
        {
            return this.Combine(UrlPath.Parse(path));
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        public UrlBuilder GetBuilder()
        {
            return this.CreateBuilder();
        }

        protected abstract UrlBuilder CreateBuilder();

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unescaped")]
        public abstract string ToUnescapedString();

        protected static string UnifySpaceEncoding(string text)
        {
            return text.Replace("+", "%20");
        }

        public abstract AbsoluteUrl ResolveAgainst(AbsoluteUrl baseUrl);

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings")]
        protected abstract string ToUrlString();

        public bool Equals(Url other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.uri, this.uri);
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as Url);
        }

        public override int GetHashCode()
        {
            return (this.uri != null ? this.uri.GetHashCode() : 0);
        }

        public static implicit operator Uri(Url url)
        {
            return url.uri;
        }

        public static bool operator ==(Url left, Url right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Url left, Url right)
        {
            return !Equals(left, right);
        }

        private static Url TryParse(string value, UriKind uriKind)
        {
            if (String.IsNullOrEmpty(value))
            {
                return null;
            }

            Uri parsedUri;
            if (Uri.TryCreate(value, uriKind, out parsedUri) == false)
            {
                return null;
            }

            return FromUri(parsedUri);
        }

        private static string EnsureSchemaDelimiter(string value)
        {
            if (value.Contains(Uri.SchemeDelimiter) == false)
                value = Uri.UriSchemeHttp + Uri.SchemeDelimiter + value;
            return value;
        }
    }
}